package karpiel.boguchwal.wojciech.przemek

import karpiel.boguchwal.wojciech.przemek.Ast.Bruijn
import karpiel.boguchwal.wojciech.przemek.Bruinizer.BruinizationException

case class Context(ctx: List[ContextElement]) {
  def getType(index: Bruijn): Ast = ctx(index.i).typeT

  def getValue(index: Bruijn): Option[Ast] = if (index.i >= ctx.size || index.i < 0) {
    throw new BruinizationException(s"NIE MA NIC W KONTEKŚCIE DLA INDEKSU ${index.i}")
  } else {
    ctx(index.i).value
  }

  def getIndexByName(name: String): Int = {
    val index = getIndexForName(name, ctx)
    if (index.isEmpty) throw new BruinizationException(s"No element named $name")
    else index.get
  }

  private def getIndexForName(name: String, ctx: List[ContextElement], index: Int = 0): Option[Int] = {
    ctx match {
      case Nil => None
      case el :: t => if (el.name.contains(name)) Some(index) else getIndexForName(name, t, index + 1)
    }
  }

  def append(typeT: Ast)(implicit cache: Cache): Context = append(ContextElement(typeT))

  def append(typeT: Ast, value: Ast)(implicit cache: Cache): Context = append(ContextElement(typeT, Some(value)))

  def append(typeT: Ast, value: Ast, name: String)(implicit cache: Cache): Context = append(ContextElement(typeT, Some(value), Some(name)))

  def append(typeT: Ast, name: String)(implicit cache: Cache): Context = append(ContextElement(typeT, None, Some(name)))


  def append(element: ContextElement)(implicit cache: Cache): Context = {
    val res = new Context(shiftBruijn(element :: ctx, 0))
    //    Bruijn(0, Option.empty).inferType(res, implicitly)
    //    element.value.foreach(_.inferType(this, implicitly).assertEquals(element.typeT)(this, implicitly)) //HUGE PERFORMANCE PENALTY

    res
  }

  private def shiftBruijn(ctx: List[ContextElement], minToAffect: Int): List[ContextElement] =
    ctx.map { case ContextElement(typeT, value, name) => ContextElement(Utils.shift(typeT, 0, 1), value.map(v => Utils.shift(v, minToAffect, 1)), name) }

  def isEmpty: Boolean = ctx.isEmpty

  def toStringQ: String = {
    if (ctx.isEmpty) {
      "<pusty kontekst>"
    } else {
      val s = new StringBuilder()
      var i = 0
      ctx.foreach { el =>
        s.append(s"Pozycja: $i\n")
        s.append(s"Typ: ${el.typeT}\n")
        el.value.foreach(n => s.append(s"Wartość: $n\n"))
        el.name.foreach(n => s.append(s"Nazwa: $n\n"))
        s.append("\n")
        i += 1
      }
      s.delete(s.size - 2, s.size) //last newLines
      s.toString()
    }
  }
}

object Context {
  val Empty: Context = new Context(Nil)
}

case class ContextElement(typeT: Ast, value: Option[Ast] = Option.empty, name: Option[String] = Option.empty) {
  override def equals(o: scala.Any): Boolean = {
    o match {
      case that: ContextElement => typeT == that.typeT && value == that.value //ignore name
      case _ => false
    }
  }
}
